using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using OpenComposite.Properties;

namespace OpenComposite.Base.Designer
{

	// ***** Comments *****
	//
	// - all coordinates are based of the coordinate system of the DesignerViewControl.MainPanel
	//	

	[XmlRoot("Route")]
	public class Route : DesignerControl, IXmlSerializable
	{
		#region Initialization
		public Route()
		{
			init();
		}
		private void init()
		{
			this.BeginUpdate();

			//this.Name = "route" + GetCount(typeof(Route));
			this.Text = this.Name;

			//
			// Context Menu Strip
			//
			this.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
			this.ContextMenuStrip.Items.Add("Remove", Resources.png_delete, new EventHandler(this.Context_Remove));

			_bSingle = false;
			_guidStart = Guid.Empty;
			_guidEnd = Guid.Empty;

			_path = new GraphicsPath();
			_points = new List<Point>();
			_penLine = new Pen(Route.DefaultLineColor);
			_penMouseOver = new Pen(Route.DefaultMouseOverLineColor);
			_penOutline = new Pen(Color.FromArgb(64, Route.DefaultLineColor));

			_dctrlStart = null;
			_dctrlEnd = null;

			//
			// _penLine
			//
			_penLine.Width = 1;
			_penLine.EndCap = LineCap.Custom;
			_penLine.CustomEndCap = new AdjustableArrowCap(5.0f, 5.0f, true);

			//
			// _penMouseOver
			//
			_penMouseOver.Width = 1;
			_penMouseOver.EndCap = LineCap.Custom;
			_penMouseOver.CustomEndCap = new AdjustableArrowCap(5.0f, 5.0f, true);

			//
			// _penOutline
			//
			_penOutline.Width = 7;
			_penOutline.EndCap = LineCap.Custom;
			_penOutline.CustomEndCap = new AdjustableArrowCap(1.0f, 1.0f, true);

			this.EndUpdate();
		}
		#endregion

		#region Public Members

		#region Properties

		public static Color DefaultLineColor
		{
			get { return Color.DarkBlue; }
		}
		public static Color DefaultMouseOverLineColor
		{
			get { return Color.Blue; }
		}

		public Color LineColor
		{
			get { return _penLine.Color; }
			set
			{
				_penLine.Color = value;
				Repaint();
			}
		}

		public GraphicsPath RoutePath
		{
			get { return _path; }
		}

		public DesignerControl StartControl
		{
			get { return _dctrlStart; }
		}
		public DesignerControl EndControl
		{
			get { return _dctrlEnd; }
		}

		public RouteDockDirection StartDockDirection
		{
			get { return _dockStart; }
			set
			{
				if ( _dockStart != value ) {
					_dockStart = value;
					OnStartDockDirectionChanged();
				}
			}
		}

		public RouteDockDirection EndDockDirection
		{
			get { return _dockEnd; }
			set
			{
				if ( _dockEnd != value ) {
					_dockEnd = value;
					OnEndDockDirectionChanged();
				}
			}
		}

		#endregion

		#region Events
		public event EventHandler RouteCreated = null;
		#endregion

		#region Methods
		public bool ConnectDesignerControls(DesignerControl controlStart, DesignerControl controlEnd)
		{
			bool genStart = false, genEnd = false;
			bool useStartConnectorAsRouteStart, useEndConnectorAsRouteEnd;
			RouteDockDirection dockS1 = RouteDockDirection.Undefined, dockE1 = RouteDockDirection.Undefined;
			RouteDockDirection dockS2 = RouteDockDirection.Undefined, dockE2 = RouteDockDirection.Undefined;
			RouteDockDirection dockStart = RouteDockDirection.Right, dockEnd = RouteDockDirection.Left;
			LineCap capStart = LineCap.NoAnchor, capEnd = LineCap.NoAnchor;
			CustomLineCap customStart = null, customEnd = null;
			Point ptStart = Point.Empty, ptEnd = Point.Empty;

			_dctrlStart = controlStart;
			_dctrlEnd = controlEnd;

			// try to get the connector and start point from controlStart
			if ( _dctrlStart is IConnectorFactory &&
				 ( (IConnectorFactory)_dctrlStart ).CreateRouteStart(_dctrlEnd,
					out _startConnector, out capStart, out customStart, out dockS1, out dockE1,
					out useStartConnectorAsRouteStart) ) {
				genStart = true;
				if ( _startConnector != null ) {
					ptStart = _startConnector.GetRouteStart(
						( dockS1 != RouteDockDirection.Undefined ? dockS1 : dockStart ), this);
					if ( useStartConnectorAsRouteStart && _startConnector is DesignerControl )
						_dctrlStart = (DesignerControl)_startConnector;
				}
			} else {
				_startConnector = null;
				capStart = LineCap.NoAnchor;
				customStart = null;
			}
			// try to get the connector and end point from controlEnd
			if ( _dctrlEnd is IConnectorFactory &&
				 ( (IConnectorFactory)_dctrlEnd ).CreateRouteEnd(_dctrlStart,
					out _endConnector, out capEnd, out customEnd, out dockS2, out dockE2,
					out useEndConnectorAsRouteEnd) ) {
				genEnd = true;
				if ( _endConnector != null ) {
					ptEnd = _endConnector.GetRouteEnd(
						( dockE2 != RouteDockDirection.Undefined ? dockE2 : dockEnd ), this);
					if ( useEndConnectorAsRouteEnd && _endConnector is DesignerControl )
						_dctrlEnd = (DesignerControl)_endConnector;
				}
			} else {
				_endConnector = null;
				capEnd = LineCap.Custom;
				customEnd = new AdjustableArrowCap(5.0f, 5.0f, true);
			}
			if ( genStart && genEnd ) {
				dockStart = selectDefined(dockS1, dockS2, dockStart);
				dockEnd = selectDefined(dockE2, dockE1, dockEnd);
			} else if ( genStart && !genEnd ) {
				dockStart = selectDefined(dockS1, dockS2, dockStart);
				dockEnd = selectDefined(dockE1, dockE2, dockEnd);
			} else if ( !genStart && genEnd ) {
				dockStart = selectDefined(dockS2, dockS1, dockStart);
				dockEnd = selectDefined(dockE2, dockE1, dockEnd);
			}
			if ( !genStart && !genEnd ) {
				// if start and end connector are not created
				// then calculate the RouteDockDirections based on the control bounds
				CalculateRouteDockDirection(_dctrlStart.GetPaintBounds(), _dctrlEnd.GetPaintBounds());
			} else {
				_dockStart = dockStart;
				_dockEnd = dockEnd;
			}

			//_points.Clear();

			//// add start point
			//if ( ptStart == Point.Empty ) {
			//    // couldn't get the start point from start connector
			//    _points.Add(GetDefaultDockPoint(_dctrlStart.GetPaintBounds(), _dockStart));
			//} else {
			//    _points.Add(ptStart);
			//}
			//// add end point
			//if ( ptEnd == Point.Empty ) {
			//    // couldn't get the end point from end connector
			//    _points.Add(GetDefaultDockPoint(_dctrlEnd.GetPaintBounds(), _dockEnd));
			//} else {
			//    _points.Add(ptEnd);
			//}

			setCaps(ref _penLine, capStart, customStart, capEnd, customEnd);
			setCaps(ref _penMouseOver, capStart, customStart, capEnd, customEnd);
			setCaps(ref _penOutline, LineCap.Flat, null, LineCap.Flat, null);
			//setCaps(ref _penOutline, capStart, customStart, capEnd, customEnd);

			OnRefreshRoute();

			//// calculate intermediate route points
			//RecalculateRoutePoints();
			//// create GraphicsPath
			//GeneratePath();
			// repaint graphics
			Repaint();

			// register events
			_dctrlStart.LocationChanged += new EventHandler(RouteStartEnd_Changed);
			_dctrlStart.SizeChanged += new EventHandler(RouteStartEnd_Changed);
			_dctrlStart.PaintBoundsChanged += new EventHandler(RouteStartEnd_Changed);
			_dctrlEnd.LocationChanged += new EventHandler(RouteStartEnd_Changed);
			_dctrlEnd.SizeChanged += new EventHandler(RouteStartEnd_Changed);
			_dctrlEnd.PaintBoundsChanged += new EventHandler(RouteStartEnd_Changed);

			_dctrlStart.Removed += new EventHandler(_dctrlStart_Removed);
			_dctrlEnd.Removed += new EventHandler(_dctrlEnd_Removed);

			OnRouteCreated();

			return true;
		}
		private RouteDockDirection selectDefined(
			RouteDockDirection dock1, RouteDockDirection dock2, RouteDockDirection dockDefault)
		{
			if ( dock1 != RouteDockDirection.Undefined )
				return dock1;
			else if ( dock2 != RouteDockDirection.Undefined )
				return dock2;
			else
				return dockDefault;
		}

		public bool ConnectDesignerControls(Guid guidStart, Guid guidEnd)
		{
			_guidStart = guidStart;
			_guidEnd = guidEnd;
			connectControlsByGuids();
			return true;
		}

		public void RefreshRoute()
		{
			OnRefreshRoute();
		}
		#endregion

		#region Overrides
		public override bool CanFocused
		{
			get
			{
				return this.Visible &&
						( this.StartControl != null && this.StartControl.Visible ) &&
						( this.EndControl != null && this.EndControl.Visible );
			}
		}
		public override bool CanMove
		{
			get { return false; }
		}
		public override bool CanSelect
		{
			get
			{
				return this.Visible &&
						( this.StartControl != null && this.StartControl.Visible ) &&
						( this.EndControl != null && this.EndControl.Visible );
			}
		}
		public override bool IsContainer
		{
			get { return false; }
		}

		public override void Paint(System.Windows.Forms.PaintEventArgs pe)
		{
			if ( this.IsMouseOver )
				pe.Graphics.DrawPath(_penMouseOver, _path);
			else
				pe.Graphics.DrawPath(_penLine, _path);
			base.Paint(pe);
			//pe.Graphics.DrawPath(_penOutline, _path);
			//pe.Graphics.DrawRectangle(Pens.Black, Rectangle.Ceiling(_path.GetBounds(null, _penLine)));
		}
		public override void Dispose()
		{
			dispose(_penLine);
			dispose(_penMouseOver);
			dispose(_penOutline);
			dispose(_path);

			if ( _dctrlStart != null ) {
				_dctrlStart.LocationChanged -= RouteStartEnd_Changed;
				_dctrlStart.SizeChanged -= RouteStartEnd_Changed;
				_dctrlStart.PaintBoundsChanged -= RouteStartEnd_Changed;

				_dctrlStart.Removed -= _dctrlStart_Removed;
			}
			if ( _dctrlEnd != null ) {
				_dctrlEnd.LocationChanged -= RouteStartEnd_Changed;
				_dctrlEnd.SizeChanged -= RouteStartEnd_Changed;
				_dctrlEnd.PaintBoundsChanged -= RouteStartEnd_Changed;

				_dctrlEnd.Removed -= _dctrlEnd_Removed;
			}

			base.Dispose();
		}

		public override bool CheckMouseOver(Point location)
		{
			lock ( lockThis ) {
				if ( this.Visible )
					return _path.IsOutlineVisible(location, _penOutline);
				else
					return false;
			}
		}

		#endregion

		#region IXmlSerializable Members

		public override XmlSchema GetSchema()
		{
			return null;
		}

		public override void ReadXml(XmlReader reader)
		{
			Guid guidStart = Guid.Empty;
			Guid guidEnd = Guid.Empty;

			while ( reader.Read() ) {
				switch ( reader.Name ) {
					case "ID": this.guidIdentifier = new Guid(reader.ReadString()); break;
					case "Name": this.Name = reader.ReadString(); break;
					case "Text": this.Text = reader.ReadString(); break;
					case "Start": guidStart = new Guid(reader.ReadString()); break;
					case "End": guidEnd = new Guid(reader.ReadString()); break;
					case "Route":
						if ( reader.NodeType == XmlNodeType.EndElement ) {
							if ( guidStart != Guid.Empty && guidEnd != Guid.Empty ) {
								this.ConnectDesignerControls(guidStart, guidEnd);
								if ( this.StartControl == null || this.EndControl == null )
									this.Remove();
							}
						}
						return;
					default: break;
				}
			}
			if ( guidStart != Guid.Empty && guidEnd != Guid.Empty ) {
				this.ConnectDesignerControls(guidStart, guidEnd);
				if ( this.StartControl == null || this.EndControl == null )
					this.Remove();
			}
		}

		public override void WriteXml(XmlWriter writer)
		{
			//writer.("Route");
			writer.WriteAttributeString("Type", this.GetType().ToString());

			writer.WriteElementString("ID", this.Identifier.ToString());
			writer.WriteElementString("Name", this.Name);
			writer.WriteElementString("Text", this.Text);
			writer.WriteElementString("Start", this.StartControl.Identifier.ToString());
			writer.WriteElementString("End", this.EndControl.Identifier.ToString());
		}

		#endregion

		#endregion

		#region Protected Members

		#region Properties
		protected Point StartPoint
		{
			get
			{
				if ( _points.Count >= 2 )
					return _points[0];
				else
					return Point.Empty;
			}
		}
		protected Point EndPoint
		{
			get
			{
				if ( _points.Count >= 2 )
					return _points[_points.Count - 1];
				else
					return Point.Empty;
			}
		}

		protected IConnector StartConnector
		{
			get { return _startConnector; }
		}
		protected IConnector EndConnector
		{
			get { return _endConnector; }
		}


		protected List<Point> Points
		{
			get { return _points; }
		}
		#endregion

		#region Methods
		// new
		protected void CalculateRouteDockDirection(Rectangle rStart, Rectangle rEnd)
		{
			try {
				int x1 = rStart.X, y1 = rStart.Y, h1 = rStart.Height, w1 = rStart.Width;
				int x2 = rEnd.X, y2 = rEnd.Y, h2 = rEnd.Height, w2 = rEnd.Width;
				int dx = 30, dy = 30;

				// t1    |     | dx |
				// ------|-----|----|------
				// t2    |    _|_   |  dy
				// ------|---|_1_|h1|------     
				// t3    |     w1   |
				// ------|-----|----|------
				// t4    |     |    |
				//    l1 | l2  | l3 | l4
				bool t1 = ( y2 + h2 + dy < y1 );
				bool t4 = ( y2 > y1 + h1 + dy );
				bool t2 = !t1 && ( y2 + h2 < y1 + h1 / 2 );
				bool t3 = !t4 && ( y2 > y1 + h1 / 2 );
				bool l1 = ( x2 + w2 + dx < x1 );
				bool l4 = ( x2 > x1 + w1 + dx );
				bool l2 = !l1 && ( x2 + w2 < x1 + w1 / 2 );
				bool l3 = !l4 && ( x2 > x1 + w1 / 2 );

				if ( l1 ) {
					_dockStart = RouteDockDirection.Left;
					_dockEnd = RouteDockDirection.Right;
				} else if ( l4 ) {
					_dockStart = RouteDockDirection.Right;
					_dockEnd = RouteDockDirection.Left;
				} else if ( t1 ) {
					_dockStart = RouteDockDirection.Top;
					_dockEnd = RouteDockDirection.Bottom;
				} else if ( t4 ) {
					_dockStart = RouteDockDirection.Bottom;
					_dockEnd = RouteDockDirection.Top;
				} else if ( t2 && l2 ) {
					_dockStart = RouteDockDirection.Left;
					_dockEnd = RouteDockDirection.Bottom;
				} else if ( t3 && l2 ) {
					_dockStart = RouteDockDirection.Left;
					_dockEnd = RouteDockDirection.Top;
				} else if ( t3 && l3 ) {
					_dockStart = RouteDockDirection.Right;
					_dockEnd = RouteDockDirection.Top;
				} else if ( t2 && l3 ) {
					_dockStart = RouteDockDirection.Right;
					_dockEnd = RouteDockDirection.Bottom;
				}
			} catch { }
		}
		protected void RecalculateRoutePoints()
		{
			int dx = 20;
			//int dy = 20;
			// remove all intermediate points
			if ( _points.Count > 2 )
				_points.RemoveRange(1, _points.Count - 2);

			// create new intermediate points based on the start and end RouteDockDirection
			Point a = _points[0];
			Point b = _points[1];
			if ( a.X != b.X && a.Y != b.Y ) {
				if ( ( _dockStart == RouteDockDirection.Left || _dockStart == RouteDockDirection.Right ) &&
					 ( _dockEnd == RouteDockDirection.Left || _dockEnd == RouteDockDirection.Right ) ) {
					if ( _dockStart == RouteDockDirection.Right &&
						 _dockEnd == RouteDockDirection.Left &&
						 a.X + dx > b.X - dx ) {
						//       S-|
						// |-------|
						// |-E
						_points.Insert(1, new Point(a.X + dx, a.Y));
						_points.Insert(2, new Point(a.X + dx, a.Y + ( ( b.Y - a.Y ) / 2 )));
						_points.Insert(3, new Point(b.X - dx, a.Y + ( ( b.Y - a.Y ) / 2 )));
						_points.Insert(4, new Point(b.X - dx, b.Y));
					} else if ( _dockStart == RouteDockDirection.Left &&
								_dockEnd == RouteDockDirection.Right &&
								a.X - dx < b.X + dx ) {
						// |-S
						// |-------|
						//       E-|
						_points.Insert(1, new Point(a.X - dx, a.Y));
						_points.Insert(2, new Point(a.X - dx, b.Y + ( ( a.Y - b.Y ) / 2 )));
						_points.Insert(3, new Point(b.X + dx, b.Y + ( ( a.Y - b.Y ) / 2 )));
						_points.Insert(4, new Point(b.X + dx, b.Y));
					} else {
						// S---
						//    |
						//    ---E
						_points.Insert(1, new Point(a.X + ( ( b.X - a.X ) / 2 ), a.Y));
						_points.Insert(2, new Point(a.X + ( ( b.X - a.X ) / 2 ), b.Y));
					}
				} else if ( ( _dockStart == RouteDockDirection.Top || _dockStart == RouteDockDirection.Bottom ) &&
					 ( _dockEnd == RouteDockDirection.Top || _dockEnd == RouteDockDirection.Bottom ) ) {
					// S
					// |
					// |---|
					//     |
					//     E
					_points.Insert(1, new Point(a.X, a.Y + ( ( b.Y - a.Y ) / 2 )));
					_points.Insert(2, new Point(b.X, a.Y + ( ( b.Y - a.Y ) / 2 )));
				} else {
					// S---   S
					//    |   |
					//    E   ---E
					if ( _dockStart == RouteDockDirection.Bottom || _dockStart == RouteDockDirection.Top )
						_points.Insert(1, new Point(a.X, b.Y));
					else
						_points.Insert(1, new Point(b.X, a.Y));
				}
			}
		}
		protected Point GetDefaultDockPoint(Rectangle bounds, RouteDockDirection dock)
		{
			Point p = new Point();
			switch ( dock ) {
				case RouteDockDirection.Top:
					p.X = bounds.Left + ( ( bounds.Right - bounds.Left ) / 2 );
					p.Y = bounds.Top;
					break;
				case RouteDockDirection.Bottom:
					p.X = bounds.Left + ( ( bounds.Right - bounds.Left ) / 2 );
					p.Y = bounds.Bottom;
					break;
				case RouteDockDirection.Right:
					p.X = bounds.Right;
					p.Y = bounds.Top + ( ( bounds.Bottom - bounds.Top ) / 2 );
					break;
				case RouteDockDirection.Left:
					p.X = bounds.Left;
					p.Y = bounds.Top + ( ( bounds.Bottom - bounds.Top ) / 2 );
					break;
			}
			return p;
		}
		protected virtual void GeneratePath()
		{
			if ( this.IsDisposed )
				return;

			_path.Reset();
			if ( _points.Count > 1 ) {
				if ( _bSingle )
					_path.AddLine(_points[0], _points[_points.Count - 1]);
				else {
					List<PointF> ptsf = new List<PointF>();
					foreach ( Point pt in _points ) {
						ptsf.Add(new PointF((float)pt.X, (float)pt.Y));
					}
					Corners corners = new Corners(ptsf.ToArray(), 5.0f);
					corners.Execute(_path);
				}
			}
			RectangleF r = _path.GetBounds(null, _penOutline);
			SetBounds((int)r.X, (int)r.Y, (int)r.Width, (int)r.Height, System.Windows.Forms.BoundsSpecified.All);
		}

		// virtual
		protected virtual void OnRefreshRoute()
		{
			refreshRoute();
		}
		protected virtual void OnRouteCreated()
		{
			EventHandler tmp = this.RouteCreated;
			if ( tmp != null )
				tmp(this, EventArgs.Empty);
		}
		protected virtual void OnStartDockDirectionChanged()
		{
			RefreshRoute();
		}
		protected virtual void OnEndDockDirectionChanged()
		{
			RefreshRoute();
		}

		// override
		protected override void OnDesignerViewChanged(EventArgs e)
		{
			base.OnDesignerViewChanged(e);
			connectControlsByGuids();
		}

		#endregion

		#endregion

		#region Private Members

		#region Variables

		private bool _bSingle;

		private Pen _penLine;
		private Pen _penMouseOver;
		private Pen _penOutline;

		private GraphicsPath _path;

		private List<Point> _points;

		private IConnector _startConnector;
		private IConnector _endConnector;

		private Guid _guidStart;
		private Guid _guidEnd;
		private DesignerControl _dctrlStart;
		private DesignerControl _dctrlEnd;

		private RouteDockDirection _dockStart;
		private RouteDockDirection _dockEnd;

		#endregion

		#region Methods

		private void refreshRoute()
		{
			_points.Clear();
			// add start point
			if ( _startConnector != null ) {
				_points.Add(_startConnector.GetRouteStart(_dockStart, this));
			} else {
				_points.Add(GetDefaultDockPoint(_dctrlStart.GetPaintBounds(), _dockStart));
			}
			// add end point
			if ( _endConnector != null ) {
				_points.Add(_endConnector.GetRouteEnd(_dockEnd, this));
			} else {
				_points.Add(GetDefaultDockPoint(_dctrlEnd.GetPaintBounds(), _dockEnd));
			}
			// calculate intermediate route points
			RecalculateRoutePoints();
			// create GraphicsPath
			GeneratePath();
		}

		private void connectControlsByGuids()
		{
			if ( this.DesignerViewControl == null )
				return;
			DesignerControl dcstart = null;
			DesignerControl dcend = null;
			dcstart = this.DesignerViewControl.FindControl(_guidStart);
			dcend = this.DesignerViewControl.FindControl(_guidEnd);
			if ( dcstart != null && dcend != null )
				this.ConnectDesignerControls(dcstart, dcend);
		}

		private void setCaps(ref Pen pen, LineCap capStart, CustomLineCap customStart, LineCap capEnd, CustomLineCap customEnd)
		{
			pen.StartCap = capStart;
			if ( capStart == LineCap.Custom && customStart != null )
				pen.CustomStartCap = customStart;
			pen.EndCap = capEnd;
			if ( capEnd == LineCap.Custom && customEnd != null )
				pen.CustomEndCap = customEnd;
		}

		#endregion

		#region Event Handlers
		private void RouteStartEnd_Changed(object sender, EventArgs e)
		{
			OnRefreshRoute();
		}

		private void _dctrlStart_Removed(object sender, EventArgs e)
		{
			this.Remove();
		}
		private void _dctrlEnd_Removed(object sender, EventArgs e)
		{
			this.Remove();
		}

		private void Context_Remove(object sender, EventArgs e)
		{
			//this.DesignerViewControl.RemoveRoute(this.StartControl, this.EndControl);
			this.Remove();
		}

		#endregion

		#endregion
	}

	#region Enum: RouteDockDirection
	[Flags]
	public enum PossibleRouteDockDirection
	{
		None = 0,
		Top = 1,
		Bottom = 2,
		Right = 4,
		Left = 8
	}
	public enum RouteDockDirection
	{
		Undefined = 0,
		Top,
		Bottom,
		Right,
		Left
	}
	#endregion
}

